/* (c)2010-2013 Andrea Caltroni (andrea.caltroni@pd.infn.it), INFN (www.infn.it)
 *
 * This file is part of the IGI Survey Portlet (from here on shortened to IGISP).
 * IGISP is free software: you can redistribute it and/or modify it 
 * under the terms of the GNU General Public License as published by the Free 
 * Software Foundation, either version 3 of the License, or (at your option) any 
 * later version.
 *
 * IGISP is distributed in the hope that it will be useful, but WITHOUT ANY 
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with IGISP. If not, see <http://www.gnu.org/licenses/>.
 */
package it.infn.grid.liferay.survey.modules.takesurvey.controller;

import it.infn.grid.liferay.survey.modules.admin.web.AnswerPropertyEditor;
import it.infn.grid.liferay.survey.modules.takesurvey.web.UserChoiceValidator;

import java.util.SortedSet;
import java.util.TreeSet;

import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.RenderRequest;
import javax.servlet.http.HttpServletRequest;

import org.ajm.spring.survey.modules.admin.model.Answer;
import org.ajm.spring.survey.modules.admin.model.AnswerPositionComparator;
import org.ajm.spring.survey.modules.admin.model.Question;
import org.ajm.spring.survey.modules.admin.model.Survey;
import org.ajm.spring.survey.modules.admin.service.AnswerService;
import org.ajm.spring.survey.modules.admin.service.QuestionService;
import org.ajm.spring.survey.modules.admin.service.SurveyService;
import org.ajm.spring.survey.modules.takesurvey.model.UserChoice;
import org.ajm.spring.survey.modules.takesurvey.service.UserChoiceService;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.support.SessionStatus;
import org.springframework.web.portlet.bind.annotation.ActionMapping;
import org.springframework.web.portlet.bind.annotation.RenderMapping;

import com.liferay.portal.util.PortalUtil;

/**
 * The handler (controller) managing the Take Survey portlet operations.
 * 
 * @author Andrea Caltroni, andrea.caltroni@pd.infn.it, INFN
 */
@RequestMapping(value="view")
@SessionAttributes(types = UserChoice.class)
@Controller(value="takeSurveyController")
public class TakeSurveyController {
    
    // ------------------------------------------------------------------------|
    // --- CONSTANTS ----------------------------------------------------------|
    // ------------------------------------------------------------------------|

    // --- CLASS VARIABLES ----------------------------------------------------|
    // ------------------------------------------------------------------------|
    // ------------------------------------------------------------------------|

    static Logger logger = Logger.getLogger(TakeSurveyController.class);
    
    // ------------------------------------------------------------------------|
    // --- INSTANCE VARIABLES -------------------------------------------------|
    // ------------------------------------------------------------------------|

    @Autowired
    @Qualifier("surveyService")
    private SurveyService surveyService;
    
    @Autowired
    @Qualifier("userChoiceService")
    private UserChoiceService userChoiceService;
    
    @Autowired
    @Qualifier("questionService")
    private QuestionService questionService;
    
    @Autowired
    @Qualifier("answerService")
    private AnswerService answerService;
    
    @Autowired
    @Qualifier("userChoiceValidator")
    private UserChoiceValidator userChoiceValidator;
    
    // --- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --|
    // ---[  CONSTRUCTORS  ]---------------------------------------------------|
    // --- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --|

    // Default no parameters bean constructor is used.
    
    // --- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --|
    // ---[  CLASS METHODS  ]--------------------------------------------------|
    // --- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --|

    // --- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --|
    // ---[  INSTANCE METHODS  ]-----------------------------------------------|
    // --- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --|

    /**
     * Setup binder for adding/editing surveys.
     */
    @InitBinder("userChoice")
    //protected void initBinder(PortletRequestDataBinder binder) {
    protected void initBinder(WebDataBinder binder) {
        
        binder.registerCustomEditor(Answer.class, new AnswerPropertyEditor(getAnswerService()));
        binder.setDisallowedFields(new String[]{"question, customDescription, ip, participant"});
        
//        if (binder.getTarget() instanceof UserAnswer) {
//            binder.setValidator(userAnswerValidator);
//        }
    }

    // ------------------------------------------------------------------------|

//    @ModelAttribute("userAnswer")
//    public UserAnswer getCommandObject(@RequestParam(value="questionID", required=false) Long questionId, 
//            @RequestParam("surveyID") Long surveyId, 
//            RenderRequest request) {
//        
//        Question question = getQuestionService().findById(questionId);
//        UserAnswer newUserAnswer = new UserAnswer();
//        newUserAnswer.setQuestion(question);
//        
//        // get request IP
//        //PortletRequest portletRequest = (PortletRequest)request.getAttribute("javax.portlet.request");
//        HttpServletRequest portletRequest = PortalUtil.getHttpServletRequest(request);
//        String requestIp = portletRequest.getRemoteAddr();
//        newUserAnswer.setIp(requestIp);
//        
//        return newUserAnswer;
//    }

    // ------------------------------------------------------------------------|
    
    /**
     * Show the user the form with the next question.
     * 
     * @param surveyId
     * @param questionId
     * @param request
     * @param model
     * @return The name of the view to be resolved to a JSP file
     */
    @RenderMapping(params="action=showQuestionForUserForm")
    public String showQuestionForUserForm(@RequestParam("surveyID") Long surveyId, 
                                 @RequestParam(value="questionID", required=false) Long questionId,
                                 RenderRequest request, Model model) {
        
        if (surveyId != null && surveyId.longValue() > 0) {
            Survey survey = getSurveyService().findById(surveyId);
            Question question = null;
            if (questionId != null && questionId.longValue() > 0) {
                // this is not the first question
                question = getQuestionService().findById(questionId);
            }
            else {
                // this is the first question
                question = getSurveyService().findNextQuestionForUser(survey, null, null);
            }
            
            if (question != null) {
                //model.addAttribute("question", question);
                
                // --- add new userChoice to the model
                UserChoice newUserChoice = new UserChoice();
                newUserChoice.setQuestion(question);
                
                // get request IP
                // NOTE Liferay-dependent code
                //PortletRequest portletRequest = (PortletRequest)request.getAttribute("javax.portlet.request");
                HttpServletRequest portletRequest = PortalUtil.getHttpServletRequest(request);
                String requestIp = portletRequest.getRemoteAddr();
                newUserChoice.setIp(requestIp);
                
                model.addAttribute("questionIdxs", getSurveyService().getQuestionIndexes(survey));
                
                // If there is only one answer, we need to put it into userAnswer.selectedAnswers
                // for it to bind in the form and be recognized to be selected.
                // If the user skips it, it's not saved anyways.
                SortedSet<Answer> answers = getQuestionService().findAnswers(question);
                
                if (answers.size() == 1) {
                    SortedSet<Answer> selectedAnswers = newUserChoice.getSelectedAnswers();
                    selectedAnswers.add(answers.iterator().next());
                    newUserChoice.setSelectedAnswers(selectedAnswers);
                }
                
                model.addAttribute("answers", answers);
                
                // TODO using the method below leads to the following:
                // failed to lazily initialize a collection of role: 
                // it.infn.grid.emisurvey.modules.survey.model.Question.answers, 
                // no session or session was closed
                //model.addAttribute("answers", question.getAnswers());
                
                // skip-to-question present? skip disabled in JSP
                model.addAttribute("skipToQuestionPresent", getQuestionService().hasSkipToQuestionAnswer(question));
                
                // question not null: add user choice to the session
                model.addAttribute("userChoice", newUserChoice);
                
                if (!getUserChoiceService().hasIp(question, requestIp)) {
                    model.addAttribute("answered", false);
                }
                else {
                    // IP found. Display already answered page
                    logger.debug("Question [" + question.getId() + "] already has an answer from IP [" + requestIp + "]");
                    model.addAttribute("answered", true);
                }
            }
            else {
                // question is NULL
                // TODO this should be fetched through lazy loading and not an
                // ad-hoc method in the service layer. (See lazy loading)
                logger.warn("We couldn't find a valid question. The survey might be empty.");
                model.addAttribute("hasQuestions", getSurveyService().hasQuestions(survey));
                return "userAknowledgement";
            }
        }
        else {
            // if there is no survey ID, then we have a serious problem up above
            logger.warn("There is no valid survey ID for TakeSurveyController.showQuestionForUserForm()");
            return "userAknowledgement";
        }
        
        return "questionForUserForm";
    }
            
    // ------------------------------------------------------------------------|

    /**
     * Store the user choice and prepare the next question.
     * 
     * @param userChoice 
     * @param bindingResult 
     * @param request 
     * @param response 
     */
    @ActionMapping(params="action=submitUserChoice")
    public void submitAnswerForm(@ModelAttribute("userChoice") UserChoice userChoice,
            BindingResult bindingResult, ActionRequest request, ActionResponse response) {
        
        Question currentQuestion = userChoice.getQuestion();
        Survey survey = currentQuestion.getSurvey();
        
        if (request.getParameter("_skip") != null) {
            
            // skipping question. Empty the selectedAnswers field (see comment in
            // showQuestionForUserForm().)
            if (userChoice.getSelectedAnswers().size() > 0) {
                SortedSet<Answer> emptyAnswers = new TreeSet<Answer>(new AnswerPositionComparator());
                userChoice.setSelectedAnswers(emptyAnswers);
            }
            
            // If a user skips a question and then comes back, she can answer 
            // questions which she wouldn't be presented had she chosen an answer 
            // with the skip-to-question parameter.
            // Either we prevent skipping if an answer has skip-to defined or we 
            // NULL those answers in the middle if she comes back and chooses an
            // answer with skip-to. Doing nothing is not a choice since we wouldn't 
            // have a uniform behavior.
            // Current solution: disable skipping in the JSP. Add "Next" button
            // is question already answered.
            
            Question nextQuestion = getSurveyService().findNextQuestionForUser(survey, currentQuestion, userChoice);
            
            if (nextQuestion != null) {
                // prepare response with next question
                response.setRenderParameter("action", "showQuestionForUserForm");
                response.setRenderParameter("surveyID", survey.getId().toString());
                response.setRenderParameter("questionID", nextQuestion.getId().toString());
            }
            else {
                // survey is finished
                response.setRenderParameter("action", "surveyFinished");
                response.setRenderParameter("surveyID", survey.getId().toString());
            }
        }
        else {
            // not skipping question
            // IP is already in the model
            getUserChoiceValidator().validate(userChoice, bindingResult);
            
            if (!bindingResult.hasErrors()) {
                getUserChoiceService().save(userChoice);
                
                Question nextQuestion = getSurveyService().findNextQuestionForUser(survey, currentQuestion, userChoice);
                
                if (nextQuestion != null) {
                    // prepare next question
                    response.setRenderParameter("action", "showQuestionForUserForm");
                    response.setRenderParameter("surveyID", survey.getId().toString());
                    response.setRenderParameter("questionID", nextQuestion.getId().toString());
                }
                else {
                    // survey is finished
                    response.setRenderParameter("action", "surveyFinished");
                    response.setRenderParameter("surveyID", survey.getId().toString());
                }
            }
            else {
                if (logger.isDebugEnabled()) {
                    for (ObjectError error: bindingResult.getAllErrors()) {
                        logger.debug("QuestionForUserForm reported [" + error.toString() + "]");
                    }
                }

                // binding errors: reload current question
                response.setRenderParameter("action", "showQuestionForUserForm");
                response.setRenderParameter("surveyID", survey.getId().toString());
                response.setRenderParameter("questionID", currentQuestion.getId().toString());
            }
        }
    }

    // ------------------------------------------------------------------------|

    /**
     * The action for showing the list of available surveys. This is the default action.
     * 
     * @param surveyId 
     * @param sessionStatus 
     * 
     * @param model 
     * @return The attribute in the model defining the action of listing all surveys.
     */
    @RenderMapping(params="action=surveyFinished")
    public String aknowledgeUser(@RequestParam("surveyID") Long surveyId, 
            SessionStatus sessionStatus, Model model) {
        
        Survey survey = getSurveyService().findById(surveyId);
        model.addAttribute("hasQuestions", getSurveyService().hasQuestions(survey));
        
        sessionStatus.setComplete();
        
        return "userAknowledgement";
    }

    // --- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --|
    // ---[  GETTERS AND SETTERS  ]--------------------------------------------|
    // --- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --|

    /**
     * @param surveyService the surveyService to set
     */
    public void setSurveyService(SurveyService surveyService) {
        this.surveyService = surveyService;
    }

    // ------------------------------------------------------------------------|

    /**
     * @return the surveyService
     */
    public SurveyService getSurveyService() {
        return surveyService;
    }

    // ------------------------------------------------------------------------|

    /**
     * @return the questionService
     */
    public QuestionService getQuestionService() {
        return questionService;
    }

    // ------------------------------------------------------------------------|

    /**
     * @param questionService the questionService to set
     */
    public void setQuestionService(QuestionService questionService) {
        this.questionService = questionService;
    }

    // ------------------------------------------------------------------------|

    /**
     * @return the userChoiceService
     */
    public UserChoiceService getUserChoiceService() {
        return userChoiceService;
    }

    // ------------------------------------------------------------------------|

    /**
     * @param userChoiceService the userChoiceService to set
     */
    public void setUserChoiceService(UserChoiceService userChoiceService) {
        this.userChoiceService = userChoiceService;
    }

    // ------------------------------------------------------------------------|

    /**
     * @param userChoiceValidator the userChoiceValidator to set
     */
    public void setUserChoiceValidator(UserChoiceValidator userChoiceValidator) {
        this.userChoiceValidator = userChoiceValidator;
    }

    // ------------------------------------------------------------------------|

    /**
     * @return the userChoiceValidator
     */
    public UserChoiceValidator getUserChoiceValidator() {
        return userChoiceValidator;
    }

    // ------------------------------------------------------------------------|

    /**
     * @param answerService the answerService to set
     */
    public void setAnswerService(AnswerService answerService) {
        this.answerService = answerService;
    }

    // ------------------------------------------------------------------------|

    /**
     * @return the answerService
     */
    public AnswerService getAnswerService() {
        return answerService;
    }

    // ------------------------------------------------------------------------|

}
